package com.kanjj.mywebrtc.network;

import android.content.Context;
import android.os.Build;
import android.util.Log;
import android.view.SurfaceView;

import androidx.annotation.RequiresApi;

import com.google.gson.Gson;
import com.google.gson.internal.LinkedTreeMap;
import com.kanjj.mywebrtc.domain.User;

import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.AudioTrack;
import org.webrtc.DataChannel;
import org.webrtc.DefaultVideoDecoderFactory;
import org.webrtc.DefaultVideoEncoderFactory;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.SessionDescription;
import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoDecoderFactory;
import org.webrtc.VideoEncoderFactory;
import org.webrtc.VideoTrack;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import io.socket.client.IO;
import io.socket.client.Socket;
import io.socket.emitter.Emitter;


public class SocketClient {

    private String TAG = "SocketClient";

    private Socket socket;
    private Gson gson = new Gson();
    private String uri;
    private String room;
    public String userId;
    private Context context;
    public PeerConnectionFactory peerConnectionFactory;
    public EglBase rootEglBase;
    public Map<String, User> users = new HashMap<>();
    // 用于视频通讯
    public Map<String, PeerConnection> peers = new HashMap<>();
    // 用于数据通信
    public Map<String, DataChannel> channels = new HashMap<>();

    public ArrayList<SurfaceViewRenderer> surfaceViews = new ArrayList<>();

    public Listener listener;

    // 信令配置
    private MediaConstraints mediaConstraints;

    // 当前视频流和音频流
    public VideoTrack videoTrack;
    public AudioTrack audioTrack;

    private static SocketClient socketClient;

    public static SocketClient newInstance(Context context, String uri, String room, String userId) {
        socketClient = new SocketClient(context, uri, room, userId);
        return socketClient;
    }

    public static SocketClient getInstance() {
        return socketClient;
    }

    private SocketClient(Context context, String uri, String room, String userId) {
        this.context = context;
        this.uri = uri;
        this.room = room;
        this.userId = userId;
        rootEglBase = EglBase.create();
        peerConnectionFactory = createPeerConnectionFactory();
        mediaConstraints = new MediaConstraints();
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
        mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
    }


    /**
     * 进行socket连接初始化
     * @return
     */
    public Socket createSocket() {
        try {
            socket = IO.socket(uri);
            socket = socket.connect();

            socket.on(Socket.EVENT_CONNECT_ERROR, args -> Log.e(TAG, "onConnectError: " + args));

            socket.on(Socket.EVENT_ERROR, args -> Log.e(TAG, "onError: " + args));
            // 获取当前room的人员信息
            socket.on("userstatechange", args -> {
                System.out.println("============================userstatechange========================");
                JSONObject jsonObject = (JSONObject) args[0];
                HashMap<String, String> map = gson.fromJson(jsonObject.toString(), HashMap.class);
                map.keySet().forEach(key -> {
                    String userId = map.get(key);
                    users.put(key, getUser(userId));
                });
            });
            // 获取已经在当前room的人员，并且创建offer信令
            socket.on("peers", args -> {
                System.out.println("============================peers========================");
                JSONArray peersArr = (JSONArray) args[0];
                try {
                    for (int i = 0; i < peersArr.length(); i++) {
                        String socketId = (String) peersArr.get(i);
                        PeerConnection peerConnection = createPeerConnection(socketId, true);
                        peers.put(socketId, peerConnection);
                        peerConnection.createOffer(new SimpleSdpObserver(SimpleSdpObserver.OFFER, socketId), mediaConstraints);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            // 进行创建answer和接收candidate
            socket.on("signal", args -> {
                System.out.println("============================signal========================");
                JSONObject obj = (JSONObject) args[0];
                String socketId = (String) args[1];
                try {
                    String type = obj.getString("type");
                    if (SimpleSdpObserver.OFFER.equals(type)) {
                        PeerConnection peerConnection = peers.get(socketId) == null ? createPeerConnection(socketId, false) : peers.get(socketId);
                        peers.put(socketId, peerConnection);
                        String description = obj.getString("sdp");
                        peerConnection.setRemoteDescription(new SimpleSdpObserver(SimpleSdpObserver.DONE, socketId), new SessionDescription(SessionDescription.Type.OFFER, description));
                        peerConnection.createAnswer(new SimpleSdpObserver(SimpleSdpObserver.ANSWER, socketId), mediaConstraints);
                    } else if (SimpleSdpObserver.ANSWER.equals(type)) {
                        PeerConnection peerConnection = peers.get(socketId);
                        String description = obj.getString("sdp");
                        peerConnection.setRemoteDescription(new SimpleSdpObserver(SimpleSdpObserver.DONE, socketId), new SessionDescription(SessionDescription.Type.ANSWER, description));
                    } else if (SimpleSdpObserver.CANDIDATE.equals(type)) {
                        IceCandidate remoteIceCandidate =
                                new IceCandidate(
                                        obj.getString("id"),
                                        obj.getInt("label"),
                                        obj.getString("candidate"));
                        peers.get(socketId).addIceCandidate(remoteIceCandidate);
                    } else {
                        Log.w(TAG, "the type is invalid: " + type);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            // 当有人员离开时调用
            socket.on("leave", args -> {
                String socketId = (String) args[0];
                System.out.println("============================leave ========================" + socketId);
                users.remove(socketId);
                DataChannel channel = channels.remove(socketId);
                if (channel != null) {
                    channel.unregisterObserver();
                    channel.close();
                    channel.dispose();
                }
                PeerConnection peerConnection = peers.remove(socketId);
                if (peerConnection != null) {
                    peerConnection.close();
                    peerConnection.dispose();
                }
                // 移除 surrender
                surfaceViews.forEach(render -> {
                    if (socketId.equals(render.getTag())) {
                        render.setTag(null);
                    }
                });
            });
            socket.emit("join", room, userId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return socket;
    }


    public PeerConnectionFactory createPeerConnectionFactory() {
        final VideoEncoderFactory encoderFactory;
        final VideoDecoderFactory decoderFactory;

        encoderFactory = new DefaultVideoEncoderFactory(
                rootEglBase.getEglBaseContext(),
                false /* enableIntelVp8Encoder */,
                true);
        decoderFactory = new DefaultVideoDecoderFactory(rootEglBase.getEglBaseContext());

        PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(context)
                .setEnableInternalTracer(true)
                .createInitializationOptions());

        PeerConnectionFactory.Builder builder = PeerConnectionFactory.builder()
                .setVideoEncoderFactory(encoderFactory)
                .setVideoDecoderFactory(decoderFactory);
        builder.setOptions(null);

        return builder.createPeerConnectionFactory();
    }


    public PeerConnection createPeerConnection(String socketId, boolean initial) {
        System.out.println("Create PeerConnection ...");

        LinkedList<PeerConnection.IceServer> iceServers = new LinkedList<PeerConnection.IceServer>();

        PeerConnection.IceServer ice_server =
                PeerConnection.IceServer.builder("turn:192.168.37.129:3478")
                        .setPassword("admin")
                        .setUsername("admin")
                        .createIceServer();

        iceServers.add(ice_server);

        PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);
        rtcConfig.candidateNetworkPolicy = PeerConnection.CandidateNetworkPolicy.ALL;
        // TCP candidates are only useful when connecting to a server that supports
        // ICE-TCP.
        rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED;
        //rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
        //rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
        rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY;
        // Use ECDSA encryption.
        //rtcConfig.keyType = PeerConnection.KeyType.ECDSA;
        // Enable DTLS for normal calls and disable for loopback calls.
        rtcConfig.enableDtlsSrtp = true;
        //rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
        PeerConnectionObserver peerConnectionObserver = new PeerConnectionObserver(socketId, initial);
        PeerConnection connection =
                peerConnectionFactory.createPeerConnection(rtcConfig,
                        peerConnectionObserver);
        if (connection == null) {
            System.out.println("Failed to createPeerConnection !");
            return null;
        }
        // 创建datachannel, 初始化创建通道，其他在onDataChannel中创建
        if (initial) {
            DataChannel.Init init = new DataChannel.Init();
            DataChannel chat = connection.createDataChannel("chatMsg", init);
            if (chat != null) {
                chat.registerObserver(new SimpleChannelObserver());
                channels.put(socketId, chat);
            }
        }
        // 添加流
        if (videoTrack != null && audioTrack != null) {
            List<String> mediaStreamLabels = Collections.singletonList("ARDAMS");
            connection.addTrack(videoTrack, mediaStreamLabels);
            connection.addTrack(audioTrack, mediaStreamLabels);
        }

        return connection;
    }


    public void sendSignal(JSONObject message) {
        socket.emit("signal", message);
    }

    public void sendSignal(JSONObject message, String socketId) {
        socket.emit("signal", message, socketId);
    }

    public void sendMsg(String msg) {
        if (StringUtils.isBlank(msg)) return;
        String message = userId + ":" + msg;
        System.out.println("=====================发送msg：" + message);
        channels.keySet().forEach(channel -> {
            DataChannel.Buffer buffer = new DataChannel.Buffer(ByteBuffer.wrap(message.getBytes()), false);
            channels.get(channel).send(buffer);
        });
    }

    public void setStream(VideoTrack videoTrack, AudioTrack audioTrack) {
        this.videoTrack = videoTrack;
        this.audioTrack = audioTrack;
    }

    /**
     * 进行清理工作
     */
    public void leaveRoom() {
        Log.i(TAG, "leaveRoom: " + room);
        if (channels != null) {
            channels.keySet().forEach(key -> {
                DataChannel dataChannel = channels.get(key);
                dataChannel.unregisterObserver();
                dataChannel.close();
                dataChannel.dispose();
            });
            channels = new HashMap<>();
        }
        if (peers != null) {
            peers.keySet().forEach(key -> {
                PeerConnection connection = peers.get(key);
                connection.close();
                connection.dispose();
            });
            peers = new HashMap<>();
        }
        peerConnectionFactory.dispose();
        users = new HashMap<>();
        if (socket != null) {
            socket.off();
            socket.disconnect();
            socket.close();
        }
    }


    private User getUser(String userId) {
        return new User(userId);
    }


    public interface Listener {
        // data channel 获取信息回调
        public void onMessage(String user, String msg);

    }

}
